/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * https://opensso.dev.java.net/public/CDDLv1.0.html or
 * opensso/legal/CDDLv1.0.txt
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at opensso/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * $Id: LogoutUtil.java,v 1.16 2009/11/20 21:41:16 exu Exp $
 *
 * Portions Copyrighted 2012-2016 ForgeRock AS.
 * Portions Copyrighted 2025 3A Systems LLC.
 */
package com.sun.identity.saml2.profile;

import static org.forgerock.openam.utils.Time.*;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.xml.soap.SOAPException;
import jakarta.xml.soap.SOAPMessage;

import com.sun.identity.saml2.common.SOAPCommunicator;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.sun.identity.plugin.session.SessionException;
import com.sun.identity.shared.encode.Base64;
import com.sun.identity.shared.encode.URLEncDec;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.shared.xml.XMLUtils;
import com.sun.identity.saml.common.SAMLUtils;
import com.sun.identity.saml.xmlsig.KeyProvider;
import com.sun.identity.saml2.assertion.EncryptedID;
import com.sun.identity.saml2.assertion.Issuer;
import com.sun.identity.saml2.assertion.NameID;
import com.sun.identity.saml2.common.SAML2Constants;
import com.sun.identity.saml2.common.SAML2Exception;
import com.sun.identity.saml2.common.SAML2Utils;
import com.sun.identity.saml2.jaxb.entityconfig.BaseConfigType;
import com.sun.identity.saml2.jaxb.metadata.EndpointType;
import com.sun.identity.saml2.jaxb.metadata.IDPSSODescriptorElement;
import com.sun.identity.saml2.jaxb.metadata.KeyDescriptorType;
import com.sun.identity.saml2.jaxb.metadata.SPSSODescriptorElement;
import com.sun.identity.saml2.jaxb.metadata.SingleLogoutServiceElement;
import com.sun.identity.saml2.key.EncInfo;
import com.sun.identity.saml2.key.KeyUtil;
import com.sun.identity.saml2.logging.LogUtil;
import com.sun.identity.saml2.meta.SAML2MetaException;
import com.sun.identity.saml2.meta.SAML2MetaManager;
import com.sun.identity.saml2.meta.SAML2MetaUtils;
import com.sun.identity.saml2.plugins.FedletAdapter;
import com.sun.identity.saml2.protocol.Extensions;
import com.sun.identity.saml2.protocol.LogoutRequest;
import com.sun.identity.saml2.protocol.LogoutResponse;
import com.sun.identity.saml2.protocol.ProtocolFactory;
import com.sun.identity.saml2.protocol.SessionIndex;
import com.sun.identity.saml2.protocol.Status;
import com.sun.identity.saml2.protocol.StatusDetail;

/**
 * This class constructs the <code>LogoutRequest</code> and executes
 * the required processing logic for sending <code>LogoutRequest</code>
 * from SP to IDP.
 */

public class LogoutUtil {
    static KeyProvider keyProvider = KeyUtil.getKeyProviderInstance(); 
    static SAML2MetaManager metaManager = null;
    static Debug debug = SAML2Utils.debug;
    
    static {
        metaManager= SAML2Utils.getSAML2MetaManager(); 
    }

    /**
     * Builds the <code>LogoutRequest</code> and executes
     * the required processing logic for sending <code>LogoutRequest</code>
     * from SP to IDP.
     *
     * @param metaAlias the requester's metaAlais.
     * @param recipientEntityID the recipient's entity ID.
     * @param recipientSLOList recipient's Single Logout Service location
     * URL list.
     * @param extensionsList Extension list for request.
     * @param binding binding used for this request.
     * @param relayState the target URL on successful Single Logout.
     * @param sessionIndex sessionIndex of the Assertion generated by the
     * Identity Provider or Service Provider.
     * @param nameID <code>NameID</code> of the Provider.
     * @param response the HttpServletResponse.
     * @param paramsMap Map of all other parameters.
     *       Following parameters names with their respective
     *       String values are allowed in this paramsMap.
     *       "realm" - MetaAlias for Service Provider. The format of
     *               this parameter is /realm_name/SP name.
     *       "RelayState" - the target URL on successful Single Logout
     *       "Destination" - A URI Reference indicating the address to
     *                       which the request has been sent.
     *       "Consent" - Specifies a URI a SAML defined identifier
     *                   known as Consent Identifiers.
     * @param config entity base config for basic auth.
     *
     * @return Logout request ID
     *
     * @throws SAML2Exception if error initiating request to IDP.
     * @throws SessionException if error initiating request to IDP.
     */
    public static StringBuffer doLogout(
            String metaAlias,
            String recipientEntityID,
            List<EndpointType> recipientSLOList,
            List extensionsList,
            String binding,
            String relayState,
            String sessionIndex,
            NameID nameID,
            HttpServletRequest request,
            HttpServletResponse response,
            Map paramsMap,
            BaseConfigType config) throws SAML2Exception, SessionException {
        EndpointType logoutEndpoint = null;
        for (EndpointType endpoint : recipientSLOList) {
            if (binding.equals(endpoint.getBinding())) {
                logoutEndpoint = endpoint;
                break;
            }
        }
        return doLogout(metaAlias, recipientEntityID, extensionsList, logoutEndpoint, relayState, sessionIndex, nameID,
                request, response, paramsMap, config);
    }

    public static StringBuffer doLogout(
            String metaAlias,
            String recipientEntityID,
            List extensionsList,
            EndpointType logoutEndpoint,
            String relayState,
            String sessionIndex,
            NameID nameID,
            HttpServletRequest request,
            HttpServletResponse response,
            Map paramsMap,
            BaseConfigType config) throws SAML2Exception, SessionException {
        StringBuffer logoutRequestID = new StringBuffer();

        String classMethod = "LogoutUtil.doLogout: ";
        String requesterEntityID = metaManager.getEntityByMetaAlias(metaAlias);
        String realm = SAML2MetaUtils.getRealmByMetaAlias(metaAlias);
        String hostEntityRole = SAML2Utils.getHostEntityRole(paramsMap);
        String location = null;
        String binding = null;
        if (logoutEndpoint != null) {
            location = logoutEndpoint.getLocation();
            binding = logoutEndpoint.getBinding();
        } else {
            debug.error(classMethod + "Unable to find the recipient's single logout service with the binding "
                    + binding);
            throw new SAML2Exception(SAML2Utils.bundle.getString("sloServiceNotfound"));
        }

        if (debug.messageEnabled()) {
            debug.message(classMethod + "Entering ..."
                    + "\nrequesterEntityID=" + requesterEntityID
                    + "\nrecipientEntityID=" + recipientEntityID
                    + "\nbinding=" + binding
                    + "\nrelayState=" + relayState
                    + "\nsessionIndex=" + sessionIndex);
        }

        // generate unique request ID
        String requestID = SAML2Utils.generateID();
        if ((requestID == null) || (requestID.length() == 0)) {
            throw new SAML2Exception(SAML2Utils.bundle.getString("cannotGenerateID"));
        }

        // retrieve data from the params map
        // destinationURI required if message is signed.
        String destinationURI = SAML2Utils.getParameter(paramsMap, SAML2Constants.DESTINATION);
        String consent = SAML2Utils.getParameter(paramsMap, SAML2Constants.CONSENT);
        Extensions extensions = createExtensions(extensionsList);
        Issuer issuer = SAML2Utils.createIssuer(requesterEntityID);

        // construct LogoutRequest
        LogoutRequest logoutReq = null;
        try {
            logoutReq = ProtocolFactory.getInstance().createLogoutRequest();
        } catch (Exception e) {
            debug.error(classMethod + "Unable to create LogoutRequest : ", e);
            throw new SAML2Exception(SAML2Utils.bundle.getString("errorCreatingLogoutRequest"));
        }

        // set required attributes / elements
        logoutReq.setID(requestID);
        logoutReq.setVersion(SAML2Constants.VERSION_2_0);
        logoutReq.setIssueInstant(newDate());
        setNameIDForSLORequest(logoutReq, nameID, realm, requesterEntityID, hostEntityRole, recipientEntityID);

        // set optional attributes / elements
        logoutReq.setDestination(XMLUtils.escapeSpecialCharacters(destinationURI));
        logoutReq.setConsent(consent);
        logoutReq.setIssuer(issuer);
        if (hostEntityRole.equals(SAML2Constants.IDP_ROLE)) {
            // use the assertion effective time (in seconds)
            int effectiveTime = SAML2Constants.ASSERTION_EFFECTIVE_TIME;
            String effectiveTimeStr = SAML2Utils.getAttributeValueFromSSOConfig(realm, requesterEntityID,
                    SAML2Constants.IDP_ROLE, SAML2Constants.ASSERTION_EFFECTIVE_TIME_ATTRIBUTE);
            if (effectiveTimeStr != null) {
                try {
                    effectiveTime = Integer.parseInt(effectiveTimeStr);
                    if (SAML2Utils.debug.messageEnabled()) {
                        SAML2Utils.debug.message(classMethod + "got effective time from config:" + effectiveTime);
                    }
                } catch (NumberFormatException nfe) {
                    SAML2Utils.debug.error(classMethod + "Failed to get assertion effective time from "
                            + "IDP SSO config: ", nfe);
                    effectiveTime = SAML2Constants.ASSERTION_EFFECTIVE_TIME;
                }
            }
            Date date = newDate();
            date.setTime(date.getTime() + effectiveTime * 1000);
            logoutReq.setNotOnOrAfter(date);
        }
        if (extensions != null) {
            logoutReq.setExtensions(extensions);
        }

        if (sessionIndex != null) {
            List list = new ArrayList();
            list.add(sessionIndex);
            logoutReq.setSessionIndex(list);
        }

        debug.message(classMethod + "Recipient's single logout service location = " + location);
        if (destinationURI == null || destinationURI.isEmpty()) {
            logoutReq.setDestination(XMLUtils.escapeSpecialCharacters(location));
        }

        if (debug.messageEnabled()) {
            debug.message(classMethod + "SLO Request before signing : ");
            debug.message(logoutReq.toXMLString(true, true));
        }

        if (binding.equals(SAML2Constants.HTTP_REDIRECT)) {
            try {
                doSLOByHttpRedirect(logoutReq.toXMLString(true, true),
                        location,
                        relayState,
                        realm,
                        requesterEntityID,
                        hostEntityRole,
                        recipientEntityID,
                        response);
                logoutRequestID.append(requestID);
                String[] data = {location};
                LogUtil.access(Level.INFO, LogUtil.REDIRECT_TO_IDP, data, null);
            } catch (Exception e) {
                debug.error("Exception :", e);
                throw new SAML2Exception(SAML2Utils.bundle.getString("errorRedirectingLogoutRequest"));
            }
        } else if (binding.equals(SAML2Constants.SOAP)) {
            logoutRequestID.append(requestID);
            signSLORequest(logoutReq, realm, requesterEntityID, hostEntityRole, recipientEntityID);
            if (debug.messageEnabled()) {
                debug.message(classMethod + "SLO Request after signing : ");
                debug.message(logoutReq.toXMLString(true, true));
            }
            location = SAML2Utils.fillInBasicAuthInfo(config, location);

            doSLOBySOAP(requestID, logoutReq, location, realm, requesterEntityID, hostEntityRole, request, response);
        } else if (binding.equals(SAML2Constants.HTTP_POST)) {
            logoutRequestID.append(requestID);
            signSLORequest(logoutReq, realm, requesterEntityID, hostEntityRole, recipientEntityID);
            if (debug.messageEnabled()) {
                debug.message(classMethod + "SLO Request after signing : ");
                debug.message(logoutReq.toXMLString(true, true));
            }
            doSLOByPOST(requestID, logoutReq.toXMLString(true, true), location, relayState, realm, requesterEntityID,
                    hostEntityRole, response, request);
        }
        SPCache.logoutRequestIDHash.put(logoutRequestID.toString(), logoutReq);
        return logoutRequestID;
    }

    static private void doSLOByHttpRedirect(
        String sloRequestXMLString,
        String sloURL,
        String relayState,
        String realm, 
        String hostEntity,
        String hostEntityRole, 
        String remoteEntity,        
        HttpServletResponse response) throws SAML2Exception, IOException {
        String method = "doSLOByHttpRedirect: ";
        // encode the xml string
        String encodedXML = SAML2Utils.encodeForRedirect(sloRequestXMLString);
        
        StringBuffer queryString = 
                new StringBuffer().append(SAML2Constants.SAML_REQUEST)
                                  .append(SAML2Constants.EQUAL)
                                  .append(encodedXML);
        
        // spec states that the relay state MUST NOT exceed 80
        // chanracters, need to have some means to pass it when it
        // exceeds 80 chars
        if ((relayState != null) && (relayState.length() > 0)) {
            String tmp = SAML2Utils.generateID();
            if (hostEntityRole.equals(SAML2Constants.IDP_ROLE)) {
                IDPCache.relayStateCache.put(tmp, relayState);
            } else {
                SPCache.relayStateHash.put(tmp, new CacheObject(relayState));
            }
            queryString.append("&").append(SAML2Constants.RELAY_STATE)
                           .append("=").append(tmp);
        }
        
        boolean needToSign = false; 
        if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
            needToSign = 
                SAML2Utils.getWantLogoutRequestSigned(realm, remoteEntity, 
                                   SAML2Constants.SP_ROLE);
        } else {
            needToSign = 
                SAML2Utils.getWantLogoutRequestSigned(realm, remoteEntity, 
                                   SAML2Constants.IDP_ROLE);
        }
        
        String signedQueryString = queryString.toString();
        if (needToSign == true) {
            signedQueryString = SAML2Utils.signQueryString(signedQueryString, 
                            realm, hostEntity, hostEntityRole);
        }
        
        String redirectURL = sloURL + (sloURL.contains("?") ? "&" : "?") +
            signedQueryString;
        if (debug.messageEnabled()) {
            debug.message(method + "LogoutRequestXMLString : " 
                                          + sloRequestXMLString);
            debug.message(method + "LogoutRedirectURL : " + sloURL);
        }
        
        response.sendRedirect(redirectURL);
    }
    
    /**
     * Performs SOAP logout, this method will send LogoutResuest to IDP using
     * SOAP binding, and process LogoutResponse.
     * @param requestID Request id.
     * @param sloRequest  a string representation of LogoutRequest.
     * @param sloURL SOAP logout URL on IDP side.
     * @param realm  a string representation of LogoutRequest.
     * @param hostEntity  host entity is sending the request.
     * @param hostRole SOAP logout URL on IDP side.
     * @throws SAML2Exception if logout failed. 
     * @throws SessionException if logout failed. 
     */ 
    private static void doSLOBySOAP(
        String requestID,
        LogoutRequest sloRequest,
        String sloURL,
        String realm,
        String hostEntity,
        String hostRole, 
        HttpServletRequest request,
        HttpServletResponse response) throws SAML2Exception, SessionException {

        String sloRequestXMLString = sloRequest.toXMLString(true, true);
        if (debug.messageEnabled()) {
            debug.message("LogoutUtil.doSLOBySOAP : SLORequestXML: " 
                + sloRequestXMLString + "\nSOAPURL : " + sloURL);
        }

        SOAPMessage resMsg = null;
        try {
            resMsg = SOAPCommunicator.getInstance().sendSOAPMessage(sloRequestXMLString, sloURL,
                    true);
        } catch (SOAPException se) {
            debug.error("Unable to send SOAPMessage to IDP ", se);
            throw new SAML2Exception(se.getMessage());
        }
        
        // get the LogoutResponse element from SOAP message
        Element respElem = SOAPCommunicator.getInstance().getSamlpElement(resMsg, "LogoutResponse");
        LogoutResponse sloResponse = ProtocolFactory.getInstance()
                .createLogoutResponse(respElem);

        String userId = null;
        // invoke SPAdapter for preSingleLogoutProcess : SP initiated SOAP
        if ((hostRole != null) && hostRole.equals(SAML2Constants.SP_ROLE)) {
            userId = SPSingleLogout.preSingleLogoutProcess(hostEntity, realm,
                request, response, null, sloRequest, sloResponse, 
                SAML2Constants.SOAP);
        }
        if (sloResponse == null) {
            debug.error("LogoutUtil.doSLOBySoap : null response");
            throw new SAML2Exception(SAML2Utils.bundle.getString(
                 "nullLogoutResponse"));
        }

        if (debug.messageEnabled()) {
            debug.message("LogoutUtil.doSLOBySOAP : " + 
                "LogoutResponse without SOAP envelope:\n" + 
                sloResponse.toXMLString());
        }
        
        Issuer resIssuer = sloResponse.getIssuer();
            String requestId = sloResponse.getInResponseTo();
        SAML2Utils.verifyResponseIssuer(
                            realm, hostEntity, resIssuer, requestId);

        String remoteEntityID = sloResponse.getIssuer().getValue();
        verifySLOResponse(sloResponse, realm, remoteEntityID, hostEntity, 
                              hostRole);
        
        boolean success = checkSLOResponse(sloResponse, requestID);
        
        if (debug.messageEnabled()) {
            debug.message("Request success : " + success);
        }
        
        if (success == false) {
            if (SPCache.isFedlet) {
                FedletAdapter fedletAdapter =
                    SAML2Utils.getFedletAdapterClass(hostEntity, realm);
                if (fedletAdapter != null) {
                    fedletAdapter.onFedletSLOFailure(
                        request, response, sloRequest, sloResponse,
                        hostEntity, remoteEntityID, SAML2Constants.SOAP); 
                }
            }
            throw new SAML2Exception(SAML2Utils.bundle.getString("sloFailed"));
        } else {
            // invoke SPAdapter for postSLOSuccess : SP inited SOAP 
            if ((hostRole != null) && hostRole.equals(SAML2Constants.SP_ROLE)) {
                if (SPCache.isFedlet) {
                    FedletAdapter fedletAdapter =
                        SAML2Utils.getFedletAdapterClass(hostEntity, realm);
                    if (fedletAdapter != null) {
                        fedletAdapter.onFedletSLOSuccess(
                            request, response, sloRequest, sloResponse,
                            hostEntity, remoteEntityID, SAML2Constants.SOAP); 
                    }
                } else {
                    SPSingleLogout.postSingleLogoutSuccess(hostEntity, realm,
                        request, response, userId, sloRequest, sloResponse, 
                        SAML2Constants.SOAP);
                }
            }
        }
    }

    private static boolean checkSLOResponse(LogoutResponse sloResponse, 
        String requestID) 
        throws SAML2Exception {
        boolean success = false;
        String retCode = sloResponse.getStatus().getStatusCode().getValue();
        
        if (retCode.equalsIgnoreCase(SAML2Constants.SUCCESS)) {
            String inResponseTo = sloResponse.getInResponseTo();
            if (inResponseTo.equals(requestID)) {
                if (debug.messageEnabled()) {
                    debug.message("LogoutUtil.doSLOBySOAP " +
                        "LogoutResponse inResponseTo matches LogoutRequest ID");
                }
            } else {
                debug.error("LogoutUtil.doSLOBySOAP " +
                    "LogoutResponse inResponseTo does not match Request ID.");
                throw new SAML2Exception(SAML2Utils.bundle.getString(
                    "inResponseToNoMatch"));
            }
            success = true;
        } else {
            if (debug.messageEnabled()) {
                debug.message("LogoutUtil.doSLOBySOAP : " +
                    "return code : " + retCode);
            }
            success = false; 
        }
        
        return success;
    }
    
    static LogoutResponse forwardToRemoteServer(LogoutRequest logoutReq,
                                                String remoteLogoutURL) {

        if (debug.messageEnabled()) {
            debug.message("LogoutUtil.forwardToRemoteServer: " +
                          "remoteLogoutURL = " + remoteLogoutURL);
        }

        try {
            SOAPMessage resMsg = SOAPCommunicator.getInstance().sendSOAPMessage(
                    logoutReq.toXMLString(true, true), remoteLogoutURL, true);

            // get the LogoutResponse element from SOAP message
            Element respElem = SOAPCommunicator.getInstance().getSamlpElement(resMsg,
                    "LogoutResponse");
            return ProtocolFactory.getInstance().createLogoutResponse(respElem);
        } catch (Exception ex) {
            if (debug.messageEnabled()) {
                debug.message("LogoutUtil.forwardToRemoteServer:", ex);
            }
        }
        return null;
    }

    static List getSessionIndex(LogoutResponse logoutRes) {
        StatusDetail statusDetail = logoutRes.getStatus().getStatusDetail();
        if (statusDetail == null) {
            return null;
        }
        List details = statusDetail.getAny();
        if (details == null || details.isEmpty()) {
            return null;
        }

        List sessionIndexList = new ArrayList();
        for(Iterator iter = details.iterator(); iter.hasNext();) {
            String detail = (String)iter.next();
            Document doc = XMLUtils.toDOMDocument(detail, debug);
            Element elem = doc.getDocumentElement();
            String localName = elem.getLocalName();
            if (SAML2Constants.SESSION_INDEX.equals(localName)) {
                sessionIndexList.add(XMLUtils.getElementString(elem));
            }
        }

        return sessionIndexList;
    }

    static void setSessionIndex(Status status, List sessionIndex) {
        try {
            StatusDetail sd=ProtocolFactory.getInstance().createStatusDetail();
            status.setStatusDetail(sd);
            if (sessionIndex != null && !sessionIndex.isEmpty()) {
                List details = new ArrayList();
                for(Iterator iter = sessionIndex.iterator(); iter.hasNext();) {
                    String si = (String)iter.next();
                    SessionIndex sIndex = ProtocolFactory.getInstance()
                                                     .createSessionIndex(si);
                    details.add(sIndex.toXMLString(true, true));
                }
                sd.setAny(details);
            }
        } catch (SAML2Exception e) {
            debug.error("LogoutUtil.setSessionIndex: ", e);
        }

    }

    /**
     * Based on the preferred SAML binding this method tries to choose the most appropriate
     * {@link SingleLogoutServiceElement} that can be used to send the logout request to. The algorithm itself is
     * simple:
     * <ul>
     *  <li>When asynchronous binding was used with the initial logout request, it is preferred to use asynchronous
     *      bindings, but if they are not available, a synchronous binding should be used.</li>
     *  <li>When synchronous binding is used with the initial request, only synchronous bindings can be used for the
     *      rest of the entities.</li>
     * </ul>
     *
     * @param sloList The list of SLO endpoints for a given entity.
     * @param preferredBinding The binding that was used to initiate the logout request.
     * @return The most appropriate SLO service location that can be used for sending the logout request. If there is
     * no appropriate logout endpoint, null is returned.
     */
    public static SingleLogoutServiceElement getMostAppropriateSLOServiceLocation(
            List<SingleLogoutServiceElement> sloList, String preferredBinding) {
        //shortcut for the case when SLO isn't supported at all
        if (sloList.isEmpty()) {
            return null;
        }

        Map<String, SingleLogoutServiceElement> sloBindings =
                new HashMap<String, SingleLogoutServiceElement>(sloList.size());
        for (SingleLogoutServiceElement sloEndpoint : sloList) {
            sloBindings.put(sloEndpoint.getBinding(), sloEndpoint);
        }

        SingleLogoutServiceElement endpoint = sloBindings.get(preferredBinding);
        if (endpoint == null) {
            //if the requested binding isn't supported let's try to find the most appropriate SLO endpoint
            if (preferredBinding.equals(SAML2Constants.HTTP_POST)) {
                endpoint = sloBindings.get(SAML2Constants.HTTP_REDIRECT);
            } else if (preferredBinding.equals(SAML2Constants.HTTP_REDIRECT)) {
                endpoint = sloBindings.get(SAML2Constants.HTTP_POST);
            }

            if (endpoint == null) {
                //we ran out of asynchronous bindings, so our only chance is to try to use SOAP binding
                //in case the preferred binding was SOAP from the beginning, then this code will just return null again
                endpoint = sloBindings.get(SAML2Constants.SOAP);
            }
        }

        return endpoint;
    }

    /**
     * Gets Single Logout Service location URL.
     *
     * @param sloList list of configured <code>SingleLogoutElement</code>.
     * @param desiredBinding desired binding of SingleLogout.
     * @return url of desiredBinding.
     */
    public static String getSLOServiceLocation(
        List sloList,
        String desiredBinding) {
        String classMethod =
            "LogoutUtil.getSLOserviceLocation: ";
        int n = sloList.size();
        if (debug.messageEnabled()) {
            debug.message(
                classMethod +
                "Number of single logout services = " +
                n);
        }
        SingleLogoutServiceElement slos = null;
        String location = null;
        String binding = null;
        for (int i=0; i<n; i++) {
            slos = (SingleLogoutServiceElement)sloList.get(i);
            if (slos != null) {
                binding = slos.getBinding();
            }
            if (debug.messageEnabled()) {
                debug.message(
                    classMethod +
                    "Single logout service binding = " +
                    binding);
            }
            if ((binding != null) && (binding.equals(desiredBinding))) {
                location = slos.getLocation();
                if (debug.messageEnabled()) {
                    debug.message(
                        classMethod +
                        "Found the single logout service "+
                        "with the desired binding");
                }
                break;
            }
        }
        return location;
    }

    /**
     * Gets Single Logout Response Service location URL.
     *
     * @param sloList list of configured <code>SingleLogoutElement</code>.
     * @param desiredBinding desired binding of SingleLogout.
     * @return url of desiredBinding.
     */
    public static String getSLOResponseServiceLocation(
        List sloList,
        String desiredBinding) {
        String classMethod =
            "LogoutUtil.getSLOResponseServiceLocation: ";
        int n = sloList.size();
        if (debug.messageEnabled()) {
            debug.message(
                classMethod +
                "Number of single logout services = " +
                n);
        }
        SingleLogoutServiceElement slos = null;
        String resLocation = null;
        String binding = null;
        for (int i=0; i<n; i++) {
            slos = (SingleLogoutServiceElement)sloList.get(i);
            if (slos != null) {
                binding = slos.getBinding();
            }
            if (debug.messageEnabled()) {
                debug.message(
                    classMethod +
                    "Single logout service binding = " +
                    binding);
            }
            if ((binding != null) && (binding.equals(desiredBinding))) {
                resLocation = slos.getResponseLocation();
                if (debug.messageEnabled()) {
                    debug.message(
                        classMethod +
                        "Found the single logout service "+
                        "with the desired binding");
                }
                break;
            }
        }
        return resLocation;
    }

    /* Creates Extensions */
    private static com.sun.identity.saml2.protocol.Extensions
        createExtensions(List extensionsList) throws SAML2Exception {
        Extensions extensions = null;
        if (extensionsList != null && !extensionsList.isEmpty()) {
            extensions = ProtocolFactory.getInstance().createExtensions();
            extensions.setAny(extensionsList);
        }
        return extensions;
    }

    /**
    * Builds the <code>LogoutResponse</code> to be sent to IDP.
     *
     * @param status status of the response.
     * @param inResponseTo inResponseTo.
     * @param issuer issuer of the response, which is SP.
     * @param realm inResponseTo.
     * @param hostRole issuer of the response, which is SP.
     * @param remoteEntity will get this response.
     *
     * @return <code>LogoutResponse</code>
     *
     */   
    public static LogoutResponse generateResponse(
        Status status,
        String inResponseTo,
        Issuer issuer,
        String realm,
        String hostRole,
        String remoteEntity) {

        if (status == null) {
            status = SAML2Utils.generateStatus(SAML2Constants.SUCCESS,
                         SAML2Utils.bundle.getString("requestSuccess"));
        }
        LogoutResponse logoutResponse = ProtocolFactory.getInstance()
        .createLogoutResponse(); 
        String responseID = SAMLUtils.generateID();

        try {
            logoutResponse.setStatus(status);
            logoutResponse.setID(responseID);
            logoutResponse.setInResponseTo(inResponseTo);
            logoutResponse.setVersion(SAML2Constants.VERSION_2_0);
            logoutResponse.setIssueInstant(newDate());
            logoutResponse.setIssuer(issuer);
        } catch (SAML2Exception e) {
            debug.error("Error in generating LogoutResponse.", e);
        }
        
        return logoutResponse;
    }

    /**
     * Sign LogoutRequest.
     *
     * @param sloRequest SLO request will be signed.
     * @param realm realm of host entity.
     * @param hostEntity entity ID of host entity.
     * @param hostEntityRole role of host entity.
     * @param remoteEntity entity ID of remote host entity.
     * @throws SAML2Exception if error in signing the request.
     */   
    public static void signSLORequest(LogoutRequest sloRequest, 
                               String realm, String hostEntity, 
                               String hostEntityRole, String remoteEntity) 
        throws SAML2Exception {
        signSLORequest(sloRequest, realm, hostEntity, 
                       hostEntityRole, remoteEntity, true);
    }
    
    static void signSLORequest(LogoutRequest sloRequest, 
                               String realm, String hostEntity,
                               String hostEntityRole, String remoteEntity,
                               boolean includeCert) 
        throws SAML2Exception {
        String method = "signSLORequest : ";
        boolean needRequestSign = false;
        
        if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
            needRequestSign = SAML2Utils.getWantLogoutRequestSigned(realm, 
                                  remoteEntity, SAML2Constants.SP_ROLE);
        } else {
            needRequestSign = SAML2Utils.getWantLogoutRequestSigned(realm, 
                                  remoteEntity, SAML2Constants.IDP_ROLE);
        }
        
        if (needRequestSign == false) {
            if (debug.messageEnabled()) {
                debug.message(method + "SLORequest doesn't need to be signed.");
            }
            return;
        }
        
        String alias = SAML2Utils.getSigningCertAlias(realm, hostEntity, hostEntityRole);
        String encryptedKeyPass = SAML2Utils.getSigningCertEncryptedKeyPass(realm, hostEntity, hostEntityRole);
        if (debug.messageEnabled()) {
            debug.message(method + "realm is : "+ realm);
            debug.message(method + "hostEntity is : " + hostEntity);
            debug.message(method + "Host Entity role is : " + hostEntityRole);
            debug.message(method + "Cert Alias is : " + alias);
            debug.message(method + "SLO Request before sign : " 
                            + sloRequest.toXMLString(true, true));
            if (encryptedKeyPass != null && !encryptedKeyPass.isEmpty()) {
                debug.message(method + "Using provided Cert KeyPass");
            }
        }

        PrivateKey signingKey;
        if (encryptedKeyPass == null || encryptedKeyPass.isEmpty()) {
            signingKey = keyProvider.getPrivateKey(alias);
        } else {
            signingKey = keyProvider.getPrivateKey(alias, encryptedKeyPass);
        }
        X509Certificate signingCert = null;
        if (includeCert) {
            signingCert = keyProvider.getX509Certificate(alias);
        }
        
        if (signingKey != null) {
            sloRequest.sign(signingKey, signingCert);
        } else {
                debug.error("Incorrect configuration for Signing Certificate.");
            throw new SAML2Exception(
                    SAML2Utils.bundle.getString("metaDataError"));
        }
        
        if (debug.messageEnabled()) {
            debug.message(method + "SLO Request after sign : " 
                                    + sloRequest.toXMLString(true, true));
        }
    }

    /**
     * Verify the signature in LogoutRequest.
     *
     * @param sloRequest SLO request will be verified.
     * @param realm realm of host entity.
     * @param remoteEntity entity ID of remote host entity.
     * @param hostEntity entity ID of host entity.
     * @param hostEntityRole role of host entity.
     * @return returns true if signature is valid.
     * @throws SAML2Exception if error in verifying the signature.
     * @throws SessionException if error in verifying the signature.
     */   
    public static boolean verifySLORequest(LogoutRequest sloRequest, 
                                    String realm, String remoteEntity, 
                                    String hostEntity, String hostEntityRole) 
        throws SAML2Exception, SessionException {
        String method = "verifySLORequest : ";
        boolean needVerifySignature = 
                SAML2Utils.getWantLogoutRequestSigned(realm, hostEntity, 
                                hostEntityRole);
        
        if (needVerifySignature == false) {
            if (debug.messageEnabled()) {
                debug.message(method+"SLORequest doesn't need to be verified.");
            }
            return true;
        }
                
        if (debug.messageEnabled()) {
            debug.message(method + "realm is : "+ realm);
            debug.message(method + "remoteEntity is : " + remoteEntity);
            debug.message(method + "Host Entity role is : " + hostEntityRole);
        }

        boolean valid = false;
        Set<X509Certificate> signingCerts;
        if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
            SPSSODescriptorElement spSSODesc = metaManager.getSPSSODescriptor(realm, remoteEntity);
            signingCerts = KeyUtil.getVerificationCerts(spSSODesc, remoteEntity, SAML2Constants.SP_ROLE);
        } else {
            IDPSSODescriptorElement idpSSODesc = metaManager.getIDPSSODescriptor(realm, remoteEntity);
            signingCerts = KeyUtil.getVerificationCerts(idpSSODesc, remoteEntity, SAML2Constants.IDP_ROLE);
        }

        if (!signingCerts.isEmpty()) {
            valid = sloRequest.isSignatureValid(signingCerts);
                if (debug.messageEnabled()) {
                debug.message(method + "Signature is : " + valid);
            }
        } else {
                debug.error("Incorrect configuration for Signing Certificate.");
            throw new SAML2Exception(
                    SAML2Utils.bundle.getString("metaDataError"));
        }
        
        return valid;
    }
    
    /**
     * Sign LogoutResponse.
     *
     * @param sloResponse SLO response will be signed.
     * @param realm realm of host entity.
     * @param hostEntity entity ID of host entity.
     * @param hostEntityRole role of host entity.
     * @param remoteEntity entity ID of remote host entity.
     * @throws SAML2Exception if error in signing the request.
     */   
    public static void signSLOResponse(LogoutResponse sloResponse, 
                                String realm, String hostEntity, 
                                String hostEntityRole, String remoteEntity) 
        throws SAML2Exception {
        signSLOResponse(sloResponse, realm, hostEntity, 
                        hostEntityRole, remoteEntity, true); 
    }
    
    static void signSLOResponse(LogoutResponse sloResponse, 
                               String realm, String hostEntity, 
                               String hostEntityRole, String remoteEntity, 
                               boolean includeCert) 
        throws SAML2Exception {
        String method = "signSLOResponse : ";
        boolean needSignResponse = false;
        
        if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
            needSignResponse = SAML2Utils.getWantLogoutResponseSigned(realm, 
                                  remoteEntity, SAML2Constants.SP_ROLE);
        } else {
            needSignResponse = SAML2Utils.getWantLogoutResponseSigned(realm, 
                                  remoteEntity, SAML2Constants.IDP_ROLE);
        }
        
        if (needSignResponse == false) {
            if (debug.messageEnabled()) {
                debug.message(method + 
                              "SLOResponse doesn't need to be signed.");
            }
            return;
        }

        String alias = SAML2Utils.getSigningCertAlias(realm, hostEntity, hostEntityRole);
        String encryptedKeyPass = SAML2Utils.getSigningCertEncryptedKeyPass(realm, hostEntity, hostEntityRole);
        if (debug.messageEnabled()) {
            debug.message(method + "realm is : "+ realm);
            debug.message(method + "hostEntity is : " + hostEntity);
            debug.message(method + "Host Entity role is : " + hostEntityRole);
            debug.message(method + "Cert Alias is : " + alias);
            if (encryptedKeyPass != null && !encryptedKeyPass.isEmpty()) {
                debug.message(method + "Using provided Cert KeyPass");
            }
        }

        PrivateKey signingKey;
        if (encryptedKeyPass == null || encryptedKeyPass.isEmpty()) {
            signingKey = keyProvider.getPrivateKey(alias);
        } else {
            signingKey = keyProvider.getPrivateKey(alias, encryptedKeyPass);
        }
        X509Certificate signingCert = null;
        if (includeCert) {
            signingCert = keyProvider.getX509Certificate(alias);
        }
        
        if (signingKey != null) {
            sloResponse.sign(signingKey, signingCert);
        } else {
                debug.error("Incorrect configuration for Signing Certificate.");
            throw new SAML2Exception(
                    SAML2Utils.bundle.getString("metaDataError"));
        }
    }

    /**
     * Verify the signature in LogoutResponse.
     *
     * @param sloResponse SLO response will be verified.
     * @param realm realm of host entity.
     * @param remoteEntity entity ID of remote host entity.
     * @param hostEntity entity ID of host entity.
     * @param hostEntityRole role of host entity.
     * @return returns true if signature is valid.
     * @throws SAML2Exception if error in verifying the signature.
     * @throws SessionException if error in verifying the signature.
     */   
    public static boolean verifySLOResponse(LogoutResponse sloResponse, 
                                     String realm, String remoteEntity, 
                                     String hostEntity, String hostEntityRole) 
        throws SAML2Exception, SessionException {
        String method = "verifySLOResponse : ";
        boolean needVerifySignature = 
                SAML2Utils.getWantLogoutResponseSigned(realm, hostEntity, 
                                hostEntityRole);
        
        if (needVerifySignature == false) {
            if (debug.messageEnabled()) {
                debug.message(method + 
                              "SLOResponse doesn't need to be verified.");
            }
            return true;
        }

        Set<X509Certificate> signingCerts;
        if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
            SPSSODescriptorElement spSSODesc = metaManager.getSPSSODescriptor(realm, remoteEntity);
            signingCerts = KeyUtil.getVerificationCerts(spSSODesc, remoteEntity, SAML2Constants.SP_ROLE);
        } else {
            IDPSSODescriptorElement idpSSODesc = metaManager.getIDPSSODescriptor(realm, remoteEntity);
            signingCerts = KeyUtil.getVerificationCerts(idpSSODesc, remoteEntity, SAML2Constants.IDP_ROLE);
        }
        
        if (!signingCerts.isEmpty()) {
            boolean valid = sloResponse.isSignatureValid(signingCerts);
            if (debug.messageEnabled()) {
                debug.message(method + "Signature is : " + valid);
            }
            return valid;
        } else {
            debug.error("Incorrect configuration for Signing Certificate.");
            throw new SAML2Exception(
                    SAML2Utils.bundle.getString("metaDataError"));
        }
    }

    public static void setNameIDForSLORequest(LogoutRequest request,
                          NameID nameID, String realm, String hostEntity,
                          String hostEntityRole, String remoteEntity)
        throws SAML2Exception, SessionException {
        String method = "setNameIDForSLORequest: ";
        boolean needEncryptIt = false;
        
        if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
            needEncryptIt = 
                SAML2Utils.getWantNameIDEncrypted(realm, remoteEntity, 
                           SAML2Constants.SP_ROLE);
        } else {
            needEncryptIt = 
                SAML2Utils.getWantNameIDEncrypted(realm, remoteEntity, 
                           SAML2Constants.IDP_ROLE);
        }
        
        if (needEncryptIt == false) {
            if (debug.messageEnabled()) {
                debug.message(method + "NamID doesn't need to be encrypted.");
            }
            request.setNameID(nameID);
            return;
        }
        
        EncInfo encryptInfo = null;
        KeyDescriptorType keyDescriptor = null;
        if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
            SPSSODescriptorElement spSSODesc =
                metaManager.getSPSSODescriptor(realm, remoteEntity);
            keyDescriptor = KeyUtil.getKeyDescriptor(spSSODesc, "encryption");
            encryptInfo = KeyUtil.getEncInfo(spSSODesc, remoteEntity,
                SAML2Constants.SP_ROLE);
        } else {
            IDPSSODescriptorElement idpSSODesc = 
                metaManager.getIDPSSODescriptor(realm, remoteEntity);
            keyDescriptor = KeyUtil.getKeyDescriptor(idpSSODesc, "encryption");
            encryptInfo = KeyUtil.getEncInfo(idpSSODesc, remoteEntity,
                SAML2Constants.IDP_ROLE);
        }

        if (debug.messageEnabled()) {
            debug.message(method + "realm is : "+ realm);
            debug.message(method + "hostEntity is : " + hostEntity);
            debug.message(method + "Host Entity role is : " + hostEntityRole);
            debug.message(method + "remoteEntity is : " + remoteEntity);
        }
        
        if (encryptInfo == null) {
            debug.error("NO meta data for encrypt Info.");
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("metaDataError"));       
        }
          
        X509Certificate certificate = KeyUtil.getCert(keyDescriptor);
        PublicKey recipientPublicKey = certificate.getPublicKey();
        EncryptedID encryptedID = nameID.encrypt(recipientPublicKey, 
                                  encryptInfo.getDataEncAlgorithm(), 
                                   encryptInfo.getDataEncStrength(), 
                                                      remoteEntity);
        request.setEncryptedID(encryptedID);
    }    

    static NameID getNameIDFromSLORequest(LogoutRequest request, 
                          String realm, String hostEntity,
                          String hostEntityRole)
        throws SAML2Exception {
        String method = "getNameIDFromSLORequest: ";

        boolean needDecryptIt = 
            SAML2Utils.getWantNameIDEncrypted(realm,hostEntity,hostEntityRole);
        
        if (needDecryptIt == false) {
            if (debug.messageEnabled()) {
                debug.message(method + "NamID doesn't need to be decrypted.");
            }
            return request.getNameID();
        }
        
        if (debug.messageEnabled()) {
            debug.message(method + "realm is : "+ realm);
            debug.message(method + "hostEntity is : " + hostEntity);
            debug.message(method + "Host Entity role is : " + hostEntityRole);
        }
        
        EncryptedID encryptedID = request.getEncryptedID();
  
        return encryptedID.decrypt(KeyUtil.getDecryptionKeys(realm, hostEntity, hostEntityRole));
    }    

    public static void sendSLOResponse(HttpServletResponse response,
        LogoutResponse sloResponse, String sloURL, String relayState,
        String realm, String hostEntity, String hostEntityRole, 
        String remoteEntity) throws SAML2Exception {

        sendSLOResponseRedirect(response, sloResponse, sloURL, relayState,
            realm, hostEntity, hostEntityRole, remoteEntity);
    }

    public static void sendSLOResponse(HttpServletResponse response, HttpServletRequest request,
        LogoutResponse sloResponse, String sloURL, String relayState,
        String realm, String hostEntity, String hostEntityRole, 
        String remoteEntity, String binding) throws SAML2Exception {

        if (SAML2Constants.HTTP_POST.equals(binding)) {
            sendSLOResponsePost(response, request, sloResponse, sloURL, relayState,
                realm, hostEntity, hostEntityRole, remoteEntity);
        } else {
            sendSLOResponseRedirect(response, sloResponse, sloURL, relayState,
                realm, hostEntity, hostEntityRole, remoteEntity);
        }
    }

    public static void sendSLOResponsePost(HttpServletResponse response, HttpServletRequest request,
        LogoutResponse sloResponse, String sloURL, String relayState,
        String realm, String hostEntity, String hostEntityRole, 
        String remoteEntity) throws SAML2Exception {

        signSLOResponse(sloResponse, realm, hostEntity, hostEntityRole,
            remoteEntity);

        String logoutResponseStr = sloResponse.toXMLString(true,true);
        String encMsg = SAML2Utils.encodeForPOST(logoutResponseStr);

        SAML2Utils.postToTarget(request, response, "SAMLResponse", encMsg, "RelayState", relayState, sloURL);
    }

    public static void sendSLOResponseRedirect(HttpServletResponse response,
                    LogoutResponse sloResponse, 
                    String sloURL,
                    String relayState,
                    String realm, 
                    String hostEntity,
                    String hostEntityRole, 
                    String remoteEntity)        
        throws SAML2Exception {

        try {
            String logoutResXMLString = sloResponse.toXMLString(true,true);

            // encode the xml string
            String encodedXML =
                SAML2Utils.encodeForRedirect(logoutResXMLString);

            StringBuffer queryString =
                new StringBuffer().append(SAML2Constants.SAML_RESPONSE)
                .append(SAML2Constants.EQUAL)
                .append(encodedXML);

            if (relayState != null && relayState.length() > 0
                    && relayState.getBytes("UTF-8").length <= 80) {
                    queryString.append("&").append(SAML2Constants.RELAY_STATE)
                    .append("=").append(URLEncDec.encode(relayState));
            }
                boolean needToSign = false; 
            if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)) {
                needToSign = 
                    SAML2Utils.getWantLogoutResponseSigned(realm, remoteEntity, 
                                   SAML2Constants.SP_ROLE);
            } else {
                needToSign = 
                    SAML2Utils.getWantLogoutResponseSigned(realm, remoteEntity, 
                                   SAML2Constants.IDP_ROLE);
            }
        
            String signedQueryString = queryString.toString();
            if (needToSign == true) {
                signedQueryString = 
                    SAML2Utils.signQueryString(signedQueryString, realm, 
                                           hostEntity, hostEntityRole); 
            }
            
            String redirectURL = sloURL+ (sloURL.contains("?") ? "&" : "?") +
                signedQueryString;

            if (debug.messageEnabled()) {
                debug.message("redirectURL :" + redirectURL);
            }
            String[] data = {sloURL};
            LogUtil.access(Level.INFO,LogUtil.REDIRECT_TO_SP,data,
                           null);

            response.sendRedirect(redirectURL);
        } catch (Exception e) {
            debug.error("Exception :",e);
            throw new SAML2Exception(SAML2Utils.bundle.getString(
                    "errorRedirectingLogoutResponse"));
        }
    } 
    
    /**
     * Returns binding information of SLO Service for remote entity 
     * from request or meta configuration.
     *
     * @param request the HttpServletRequest.
     * @param metaAlias entityID of hosted entity.
     * @param hostEntityRole Role of hosted entity.
     * @param remoteEntityID entityID of remote entity.
     * @return return true if the processing is successful.
     * @throws SAML2Exception if no binding information is configured.
     */
    public static String getSLOBindingInfo(HttpServletRequest request,
                                 String metaAlias,
                                 String hostEntityRole,
                                 String remoteEntityID)
                                 throws SAML2Exception {
        String binding = request.getParameter(SAML2Constants.BINDING);

        try {
            if (binding == null) {
                String realm = SAML2MetaUtils.getRealmByMetaAlias(metaAlias);
                SingleLogoutServiceElement sloService =
                    getSLOServiceElement(realm, remoteEntityID,
                                       hostEntityRole, null);
                if (sloService != null) {
                    binding = sloService.getBinding();
                }
            }
        } catch (SessionException e) {
            debug.error("Invalid SSOToken", e);
            throw new SAML2Exception(
                          SAML2Utils.bundle.getString("metaDataError"));       
        }
        
        if (binding == null) {
            debug.error("Incorrect configuration for SingleLogout Service.");
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("metaDataError"));
        }
        return binding;
    }

    private static SingleLogoutServiceElement getSLOServiceElement(
                    String realm, String entityID,  
                    String hostEntityRole, String binding)
        throws SAML2MetaException, SessionException, SAML2Exception {
        SingleLogoutServiceElement sloService = null;
        String method = "getSLOServiceElement: ";
        
        if (debug.messageEnabled()) {
            debug.message(method + "Realm : " + realm);
            debug.message(method + "Entity ID : " + entityID);
            debug.message(method + "Host Entity Role : " + hostEntityRole);
        }

        if (hostEntityRole.equalsIgnoreCase(SAML2Constants.SP_ROLE)) {
            sloService = getIDPSLOConfig(realm, entityID, binding);
        } else if (hostEntityRole.equalsIgnoreCase(SAML2Constants.IDP_ROLE)){
            sloService = getSPSLOConfig(realm, entityID, binding);
        } else {
            debug.error("Hosted Entity role is missing .");
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("nullIDPEntityID"));
        }
        
        return sloService;
    }

    /**
     * Returns first SingleLogout configuration in an entity under
     * the realm.
     * @param realm The realm under which the entity resides.
     * @param entityId ID of the entity to be retrieved.
     * @param binding bind type need to has to be matched.
     * @return <code>SingleLogoutServiceElement</code> for the entity or null
     * @throws SAML2MetaException if unable to retrieve the first identity
     *                            provider's SSO configuration.
     * @throws SessionException invalid or expired single-sign-on session
     */
    static public SingleLogoutServiceElement getIDPSLOConfig(
                                                 String realm, 
                                                 String entityId,
                                                 String binding)
        throws SAML2MetaException, SessionException {
        SingleLogoutServiceElement slo = null;

        IDPSSODescriptorElement idpSSODesc = 
                    metaManager.getIDPSSODescriptor(realm, entityId);
        if (idpSSODesc == null) {
                debug.error("Identity Provider SSO config is missing.");
            return null;
        }

        List list = idpSSODesc.getSingleLogoutService();

        if ((list != null) && !list.isEmpty()) {
            if (binding == null) {
                return (SingleLogoutServiceElement)list.get(0);
            }
            Iterator it = list.iterator();
            while (it.hasNext()) {
                slo = (SingleLogoutServiceElement)it.next();  
                if (binding.equalsIgnoreCase(slo.getBinding())) {
                    break;
                }
            }
        }

        return slo;
    }

    /**
     * Returns first SingleLogout configuration in an entity under
     * the realm.
     * @param realm The realm under which the entity resides.
     * @param entityId ID of the entity to be retrieved.
     * @param binding bind type need to has to be matched.
     * @return <code>SingleLogoutServiceElement</code> for the entity or null
     * @throws SAML2MetaException if unable to retrieve the first identity
     *                            provider's SSO configuration.
     * @throws SessionException invalid or expired single-sign-on session
     */
    static public SingleLogoutServiceElement getSPSLOConfig(
                                                String realm, String entityId,
                                                String binding)
        throws SAML2MetaException, SessionException {
        SingleLogoutServiceElement slo = null;

        SPSSODescriptorElement spSSODesc = 
                          metaManager.getSPSSODescriptor(realm, entityId);
        if (spSSODesc == null) {
            return null;
        }

        List list = spSSODesc.getSingleLogoutService();

        if ((list != null) && !list.isEmpty()) {
            if (binding == null) {
                return (SingleLogoutServiceElement)list.get(0);
            }
            Iterator it = list.iterator();
            while (it.hasNext()) {
                slo = (SingleLogoutServiceElement)it.next();  
                if (binding.equalsIgnoreCase(slo.getBinding())) {
                    break;
                }
            }
        }

        return slo;
    }

    /**
     * Returns the extensions list
     * @param paramsMap request paramsMap has extensions
     * @return <code>List</code> for extensions params
     */
    public static List getExtensionsList(Map paramsMap) {
        List extensionsList = null;
        // TODO Get the Extensions list from request parameter
        return extensionsList;
    }

    private static void doSLOByPOST(String requestID,
        String sloRequestXMLString, String sloURL, String relayState,
        String realm, String hostEntity, String hostRole, 
        HttpServletResponse response, HttpServletRequest request) throws SAML2Exception, SessionException {

        if (debug.messageEnabled()) {
            debug.message("LogoutUtil.doSLOByPOST : SLORequestXML: " 
                + sloRequestXMLString + "\nPOSTURL : " + sloURL);
            debug.message("LogoutUtil.doSLOByPOST : relayState : " 
                + sloRequestXMLString + "\nPOSTURL : " + relayState);
        }

	    String encMsg = SAML2Utils.encodeForPOST(sloRequestXMLString);
	    SAML2Utils.postToTarget(request, response, "SAMLRequest", encMsg,
                "RelayState", relayState, sloURL);
    }

    static LogoutResponse getLogoutResponseFromPost(String samlResponse,
        HttpServletResponse response) throws SAML2Exception {

        if (samlResponse == null) {
            throw new SAML2Exception(SAML2Utils.bundle.getString(
                "missingLogoutResponse"));
        }

        LogoutResponse resp = null;
        ByteArrayInputStream bis = null;
        try {
            byte[] raw = Base64.decode(samlResponse);
            if (raw != null) {
                bis = new ByteArrayInputStream(raw);
                Document doc = XMLUtils.toDOMDocument(bis, debug);
                if (doc != null) {
                    resp = ProtocolFactory.getInstance().
                        createLogoutResponse(doc.getDocumentElement());
                }
            }
        } catch (Exception e) {
            debug.error("LogoutUtil.getLogoutResponseFromPost:", e);
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (Exception ie) {
                    if (debug.messageEnabled()) {
                        debug.message("LogoutUtil.getLogoutResponseFromPost:",
                            ie);
                    }
                }
            }
        }

        if (resp == null) {
            throw new SAML2Exception(SAML2Utils.bundle.getString(
                "errorGettingLogoutResponse"));
        }
        return resp;
    }

    static LogoutRequest getLogoutRequestFromPost(String samlRequest,
        HttpServletResponse response) throws SAML2Exception {
 
        if (samlRequest == null) {
            throw new SAML2Exception(SAML2Utils.bundle.getString(
                "missingLogoutRequest"));
        }

        LogoutRequest req = null;
        ByteArrayInputStream bis = null;
        try {
            byte[] raw = Base64.decode(samlRequest);
            if (raw != null) {
                bis = new ByteArrayInputStream(raw);
                Document doc = XMLUtils.toDOMDocument(bis, debug);
                if (doc != null) {
                    req = ProtocolFactory.getInstance().
                        createLogoutRequest(doc.getDocumentElement());
                }
            }
        } catch (Exception e) {
            debug.error("LogoutUtil.getLogoutRequestFromPost:", e);
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (Exception ie) {
                    if (debug.messageEnabled()) {
                        debug.message("LogoutUtil.getLogoutRequestFromPost:",
                            ie);
                    }
                }
            }
        }
        if (req == null) {
            throw new SAML2Exception(SAML2Utils.bundle.getString(
                "errorGettingLogoutRequest"));
        }
        return req;
    }
}

